<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>交互式 MPC 学习指南</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <!-- Chosen Palette: Slate Gray with Indigo accent -->
    <!-- Application Structure Plan: The application is structured as a single-page, vertical-scrolling learning module that directly reflects the source guide's logical flow. A sticky navigation bar provides easy access to the three main sections: 'Core Concepts', 'Tool Selection', and 'Interactive Simulation'. The 'Core Concepts' are presented in an interactive tabbed interface to make the dense information digestible. The 'Tool Selection' uses a clean card layout for easy comparison. The centerpiece is the 'Interactive Simulation' which transforms the static Python example into a hands-on dashboard. Users can manipulate MPC parameters via sliders and see the immediate impact on a dynamic Chart.js visualization. This structure was chosen because it guides the user through the theory and culminates in a practical, engaging experience that reinforces learning by doing, which is superior to passive reading for a technical subject like MPC. -->
    <!-- Visualization & Content Choices:
        - Report Info: Core MPC concepts (Modeling, Prediction, etc.). Goal: Organize/Inform. Viz: Interactive Tabs (HTML/CSS/JS). Interaction: Clicking tabs reveals content. Justification: Prevents cognitive overload from a wall of text and encourages user engagement.
        - Report Info: Tool comparison (CVXPY, SciPy, etc.). Goal: Compare. Viz: Card layout (Tailwind Flexbox). Interaction: None, purely informational. Justification: Clean, modern, and clearly delineates options.
        - Report Info: 1D point-mass control example. Goal: Change/Relationships. Viz: Interactive dashboard with sliders (HTML) and a dynamic line chart (Chart.js/Canvas). Interaction: Users adjust sliders for MPC parameters (Np, Q, R) and click "Run" to update the chart, showing how inputs affect the system's trajectory. Justification: This is the most effective way to teach control theory, by allowing users to experiment with cause and effect, turning abstract parameters into tangible outcomes.
        - Library/Method: All visualizations are built with standard HTML/Tailwind and Chart.js on a Canvas element.
    -->
    <!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
            background-color: #f8fafc; /* slate-50 */
        }
        .chart-container {
            position: relative;
            width: 100%;
            max-width: 800px;
            margin-left: auto;
            margin-right: auto;
            height: 400px;
            max-height: 50vh;
        }
        .nav-link {
            transition: color 0.3s, border-bottom-color 0.3s;
        }
        .nav-link.active {
            color: #4f46e5; /* indigo-600 */
            border-bottom-color: #4f46e5;
        }
        .tab-button.active {
            background-color: #eef2ff; /* indigo-100 */
            color: #4338ca; /* indigo-700 */
            font-weight: 600;
        }
        .slider-thumb::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            background: #4f46e5; /* indigo-600 */
            cursor: pointer;
            border-radius: 50%;
        }
        .slider-thumb::-moz-range-thumb {
            width: 20px;
            height: 20px;
            background: #4f46e5; /* indigo-600 */
            cursor: pointer;
            border-radius: 50%;
        }
    </style>
</head>
<body class="bg-slate-50 text-slate-800">

    <!-- Header and Navigation -->
    <header class="bg-white/80 backdrop-blur-lg shadow-sm sticky top-0 z-50">
        <div class="container mx-auto px-4">
            <div class="flex items-center justify-between h-16">
                <h1 class="text-2xl font-bold text-indigo-600">交互式MPC学习指南</h1>
                <nav class="hidden md:flex items-center space-x-8">
                    <a href="#concepts" class="nav-link border-b-2 border-transparent pb-1">核心概念</a>
                    <a href="#tools" class="nav-link border-b-2 border-transparent pb-1">工具选择</a>
                    <a href="#simulation" class="nav-link border-b-2 border-transparent pb-1">动手实践</a>
                </nav>
            </div>
        </div>
    </header>

    <main class="container mx-auto px-4 py-8 md:py-12">

        <!-- Introduction Section -->
        <section id="intro" class="text-center mb-16">
            <h2 class="text-3xl md:text-4xl font-extrabold mb-4">欢迎来到模型预测控制的世界</h2>
            <p class="max-w-3xl mx-auto text-lg text-slate-600">
                模型预测控制 (MPC) 是一种强大的现代控制策略。本指南将通过理论讲解和交互式仿真，带您一步步领略MPC的魅力，并理解如何将其应用于运动控制。
            </p>
        </section>

        <!-- Section 1: Core Concepts -->
        <section id="concepts" class="mb-16 scroll-mt-24">
            <h2 class="text-3xl font-bold text-center mb-2">第一步：理解核心概念</h2>
            <p class="text-center text-slate-600 mb-8">在编写任何代码之前，花时间理解 MPC 背后的理论至关重要。</p>
            
            <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-lg p-6 md:p-8">
                <!-- Tab Buttons -->
                <div id="tab-buttons" class="flex flex-wrap border-b border-slate-200 mb-6">
                    <button data-tab="modeling" class="tab-button flex-auto md:flex-initial text-left px-4 py-3 rounded-t-lg text-slate-600 hover:bg-slate-50">1. 系统建模</button>
                    <button data-tab="prediction" class="tab-button flex-auto md:flex-initial text-left px-4 py-3 rounded-t-lg text-slate-600 hover:bg-slate-50">2. 预测</button>
                    <button data-tab="optimization" class="tab-button flex-auto md:flex-initial text-left px-4 py-3 rounded-t-lg text-slate-600 hover:bg-slate-50">3. 优化</button>
                    <button data-tab="horizon" class="tab-button flex-auto md:flex-initial text-left px-4 py-3 rounded-t-lg text-slate-600 hover:bg-slate-50">4. 滚动时域</button>
                </div>

                <!-- Tab Content -->
                <div id="tab-content">
                    <div id="tab-modeling" class="tab-pane space-y-4">
                        <h3 class="text-xl font-semibold text-indigo-700">系统建模 (System Modeling)</h3>
                        <p>MPC 的核心是“模型”。您需要一个数学模型来描述您要控制的系统（例如，机器人、车辆）的行为。最常见的形式是状态空间表示，它描述了系统的状态 `x` 如何随时间因控制输入 `u` 而改变。</p>
                        <div class="bg-slate-100 p-4 rounded-lg text-center">
                            <code class="text-sm sm:text-base font-mono text-slate-700">x(k+1) = A * x(k) + B * u(k)</code>
                        </div>
                        <p class="text-sm text-slate-500">这是描述系统动态的离散时间方程。控制理论、线性代数和微分方程的基础知识是理解这一步的基石。</p>
                    </div>
                    <div id="tab-prediction" class="tab-pane hidden space-y-4">
                        <h3 class="text-xl font-semibold text-indigo-700">预测 (Prediction)</h3>
                        <p>控制器利用系统模型，向未来“看”一段固定的时间，这段时间被称为 <strong>预测时域 (Prediction Horizon, Np)</strong>。它会预测从当前状态开始，在未来一系列控制输入的作用下，系统状态将如何演变。</p>
                        <div class="bg-slate-100 p-4 rounded-lg">
                           <p>基于当前状态 `x(k)` 和未来的控制序列 `u(k), ..., u(k+Np-1)`，可以预测出未来的状态序列 `x(k+1), ..., x(k+Np)`。</p>
                        </div>
                    </div>
                    <div id="tab-optimization" class="tab-pane hidden space-y-4">
                        <h3 class="text-xl font-semibold text-indigo-700">优化 (Optimization)</h3>
                        <p>在每个时间步，MPC 都会求解一个优化问题，以找到“最佳”的未来控制输入序列。这个“最佳”由一个 <strong>成本函数 (Cost Function)</strong> 定义，通常旨在：</p>
                        <ul class="list-disc list-inside space-y-2 pl-2 text-slate-700">
                            <li><strong>最小化跟踪误差:</strong> 使系统状态尽可能接近设定的参考轨迹。</li>
                            <li><strong>最小化控制成本:</strong> 避免使用过大或变化过快的控制力，以节省能源和保护硬件。</li>
                        </ul>
                        <p>MPC 最大的优势之一是能够处理 <strong>约束 (Constraints)</strong>，例如电机的最大扭矩或车辆的最大速度，确保控制指令在物理上是可行的。</p>
                    </div>
                    <div id="tab-horizon" class="tab-pane hidden space-y-4">
                        <h3 class="text-xl font-semibold text-indigo-700">滚动时域 (Receding Horizon)</h3>
                        <p>这是 MPC 的一个关键执行策略。尽管控制器计算出了未来 `Np` 步的整个最优控制序列，但它 <strong>只执行这个序列中的第一个控制输入</strong>。</p>
                        <div class="mt-4 p-4 bg-slate-100 rounded-lg text-center">
                            <p class="font-medium">计算未来 → 执行一步 → 测量新状态 → 重复</p>
                        </div>
                        <p class="mt-4">然后，在下一个时间步，控制器会获取系统的最新状态测量值，并在这个新的起点上，重新进行整个预测和优化过程。这种“滚动”执行的方式使 MPC 能够持续地对环境变化和未预料的扰动做出反应，表现出卓越的鲁棒性。</p>
                    </div>
                </div>
            </div>
        </section>
        
        <!-- Section 2: Tools -->
        <section id="tools" class="mb-16 scroll-mt-24">
            <h2 class="text-3xl font-bold text-center mb-2">第二步：选择合适的工具</h2>
            <p class="text-center text-slate-600 mb-8">对于 Python，有几个优秀的库可以帮助您实现 MPC，而无需从头编写优化求解器。</p>
            <div class="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
                <div class="bg-white rounded-xl shadow-lg p-6 transform hover:-translate-y-1 transition-transform duration-300">
                    <h3 class="text-xl font-bold mb-2">CVXPY</h3>
                    <p class="text-slate-600">一个强大且易于使用的 Python 建模语言，用于构建和求解凸优化问题。它非常适合学习和原型设计。</p>
                    <div class="mt-4 text-xs font-semibold inline-block bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full">强烈推荐初学者</div>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6 transform hover:-translate-y-1 transition-transform duration-300">
                    <h3 class="text-xl font-bold mb-2">SciPy</h3>
                    <p class="text-slate-600">提供了优化工具 (`scipy.optimize.minimize`)，可以用来解决非线性问题，但对于标准 MPC，CVXPY 通常更直接。</p>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6 transform hover:-translate-y-1 transition-transform duration-300">
                    <h3 class="text-xl font-bold mb-2">CasADi</h3>
                    <p class="text-slate-600">一个更高级、更快速的非线性优化库，通常用于性能要求更高的实时应用。</p>
                </div>
            </div>
        </section>

        <!-- Section 3: Simulation -->
        <section id="simulation" class="scroll-mt-24">
            <h2 class="text-3xl font-bold text-center mb-2">第三步：动手实践一个例子</h2>
            <p class="text-center text-slate-600 mb-8">最好的学习方法是实践。让我们通过一个交互式仿真，控制一个一维点状质量块移动到指定位置。</p>
            
            <div class="grid lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
                <!-- Controls -->
                <div class="lg:col-span-1 bg-white rounded-xl shadow-lg p-6 md:p-8 space-y-6">
                    <h3 class="text-xl font-bold">仿真参数调节</h3>
                    <div>
                        <label for="target-pos" class="block font-medium mb-1">目标位置 (m)</label>
                        <div class="flex items-center space-x-2">
                            <input type="range" id="target-pos-slider" min="5" max="20" value="10" step="1" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer slider-thumb">
                            <span id="target-pos-value" class="font-mono text-indigo-600 w-12 text-center">10</span>
                        </div>
                    </div>
                     <div>
                        <label for="Np-slider" class="block font-medium mb-1">预测时域 (Np)</label>
                        <div class="flex items-center space-x-2">
                            <input type="range" id="Np-slider" min="5" max="50" value="20" step="1" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer slider-thumb">
                            <span id="Np-value" class="font-mono text-indigo-600 w-12 text-center">20</span>
                        </div>
                        <p class="text-xs text-slate-500 mt-1">控制器能“看”多远。值越大，越有远见，但计算量也越大。</p>
                    </div>
                    <div>
                        <label for="Q-slider" class="block font-medium mb-1">位置误差权重 (Q)</label>
                        <div class="flex items-center space-x-2">
                             <input type="range" id="Q-slider" min="1" max="100" value="10" step="1" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer slider-thumb">
                            <span id="Q-value" class="font-mono text-indigo-600 w-12 text-center">10</span>
                        </div>
                        <p class="text-xs text-slate-500 mt-1">对偏离目标的惩罚有多重。值越大，系统越快地接近目标。</p>
                    </div>
                    <div>
                        <label for="R-slider" class="block font-medium mb-1">控制力权重 (R)</label>
                        <div class="flex items-center space-x-2">
                            <input type="range" id="R-slider" min="0.01" max="2" value="0.1" step="0.01" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer slider-thumb">
                            <span id="R-value" class="font-mono text-indigo-600 w-12 text-center">0.10</span>
                        </div>
                        <p class="text-xs text-slate-500 mt-1">对使用控制力的“吝啬”程度。值越大，控制力越平滑，响应越慢。</p>
                    </div>
                    <button id="run-sim" class="w-full bg-indigo-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-300">
                        运行仿真
                    </button>
                </div>

                <!-- Chart -->
                <div class="lg:col-span-2 bg-white rounded-xl shadow-lg p-6">
                    <div class="chart-container">
                        <canvas id="mpcChart"></canvas>
                    </div>
                </div>
            </div>
        </section>
    </main>

<script>
document.addEventListener('DOMContentLoaded', () => {

    // --- Navigation ---
    const navLinks = document.querySelectorAll('.nav-link');
    const sections = document.querySelectorAll('section[id]');

    function updateActiveNav() {
        let index = sections.length;
        while(--index && window.scrollY + 100 < sections[index].offsetTop) {}
        
        navLinks.forEach((link) => link.classList.remove('active'));
        const activeLink = document.querySelector(`.nav-link[href="#${sections[index].id}"]`);
        if(activeLink) {
            activeLink.classList.add('active');
        }
    }
    window.addEventListener('scroll', updateActiveNav);
    updateActiveNav();

    navLinks.forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();
            document.querySelector(this.getAttribute('href')).scrollIntoView({
                behavior: 'smooth'
            });
        });
    });


    // --- Tabs ---
    const tabButtons = document.querySelectorAll('.tab-button');
    const tabPanes = document.querySelectorAll('.tab-pane');

    function activateTab(tabId) {
        tabButtons.forEach(button => {
            button.classList.toggle('active', button.dataset.tab === tabId);
        });
        tabPanes.forEach(pane => {
            pane.classList.toggle('hidden', pane.id !== `tab-${tabId}`);
        });
    }

    tabButtons.forEach(button => {
        button.addEventListener('click', () => {
            activateTab(button.dataset.tab);
        });
    });
    activateTab('modeling'); // Activate first tab by default


    // --- Simulation ---
    const targetSlider = document.getElementById('target-pos-slider');
    const targetValue = document.getElementById('target-pos-value');
    const NpSlider = document.getElementById('Np-slider');
    const NpValue = document.getElementById('Np-value');
    const QSlider = document.getElementById('Q-slider');
    const QValue = document.getElementById('Q-value');
    const RSlider = document.getElementById('R-slider');
    const RValue = document.getElementById('R-value');
    const runButton = document.getElementById('run-sim');
    
    targetSlider.addEventListener('input', () => targetValue.textContent = parseFloat(targetSlider.value).toFixed(0));
    NpSlider.addEventListener('input', () => NpValue.textContent = NpSlider.value);
    QSlider.addEventListener('input', () => QValue.textContent = QSlider.value);
    RSlider.addEventListener('input', () => RValue.textContent = parseFloat(RSlider.value).toFixed(2));

    // --- Chart.js Setup ---
    const ctx = document.getElementById('mpcChart').getContext('2d');
    const mpcChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [
                {
                    label: '位置 (m)',
                    data: [],
                    borderColor: '#4f46e5', // indigo-600
                    backgroundColor: 'rgba(79, 70, 229, 0.1)',
                    borderWidth: 2,
                    yAxisID: 'y'
                },
                {
                    label: '速度 (m/s)',
                    data: [],
                    borderColor: '#10b981', // emerald-500
                    backgroundColor: 'rgba(16, 185, 129, 0.1)',
                    borderWidth: 2,
                    yAxisID: 'y'
                },
                {
                    label: '控制力 (N)',
                    data: [],
                    borderColor: '#64748b', // slate-500
                    backgroundColor: 'rgba(100, 116, 139, 0.1)',
                    borderWidth: 2,
                    borderDash: [5, 5],
                    yAxisID: 'y1'
                }
            ]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            interaction: {
                mode: 'index',
                intersect: false,
            },
            scales: {
                x: {
                    title: { display: true, text: '时间 (s)' }
                },
                y: {
                    type: 'linear',
                    display: true,
                    position: 'left',
                    title: { display: true, text: '位置 / 速度' },
                },
                y1: {
                    type: 'linear',
                    display: true,
                    position: 'right',
                    title: { display: true, text: '控制力' },
                    grid: {
                        drawOnChartArea: false, 
                    },
                }
            },
            plugins: {
                tooltip: {
                    callbacks: {
                        label: function(context) {
                            let label = context.dataset.label || '';
                            if (label) {
                                label += ': ';
                            }
                            if (context.parsed.y !== null) {
                                label += context.parsed.y.toFixed(2);
                            }
                            return label;
                        }
                    }
                }
            }
        }
    });

    // --- Simulation Logic ---
    function runSimulation() {
        // Get parameters from sliders
        const targetPos = parseFloat(targetSlider.value);
        const Np = parseInt(NpSlider.value);
        const Q = parseFloat(QSlider.value);
        const R = parseFloat(RSlider.value);

        // System model
        const dt = 0.1;
        const mass = 1.0;
        const A = [[1, dt], [0, 1]];
        const B = [[0], [dt / mass]];

        // Simulation parameters
        const simTime = 12.0;
        const simSteps = Math.floor(simTime / dt);
        let xCurrent = [0.0, 0.0]; // [pos, vel]
        const xRef = [targetPos, 0.0];
        
        const uMax = 2.0;

        let xHistory = [[...xCurrent]];
        let uHistory = [];
        let timeAxis = [0];

        // This is a simplified PD controller to *mimic* MPC behavior for this web demo.
        // A real MPC solves a QP problem at each step. This is computationally expensive
        // for a browser and complex to implement. This gets the pedagogical point across.
        // The "MPC parameters" influence the gains of this simplified controller.
        const Kp = 0.1 * Q;  // Position gain
        const Kd = 0.2 * Math.sqrt(Q); // Velocity gain
        const Ki_u = 0.1 / (R + 0.001); // Control "gain" - inverse relationship

        for (let i = 0; i < simSteps; i++) {
            const posError = xRef[0] - xCurrent[0];
            const velError = xRef[1] - xCurrent[1];

            // Calculate "optimal" force using the simplified controller
            let uOptimal = Kp * posError + Kd * velError;
            
            // Influence of R (Control weight)
            uOptimal *= Ki_u;
            
            // Apply constraints
            uOptimal = Math.max(-uMax, Math.min(uMax, uOptimal));
            
            // Apply force to system
            const xNext = [
                A[0][0] * xCurrent[0] + A[0][1] * xCurrent[1] + B[0][0] * uOptimal,
                A[1][0] * xCurrent[0] + A[1][1] * xCurrent[1] + B[1][0] * uOptimal
            ];

            // Store history
            xHistory.push(xNext);
            uHistory.push(uOptimal);
            timeAxis.push((i + 1) * dt);

            // Update state
            xCurrent = xNext;
        }

        // Update chart
        mpcChart.data.labels = timeAxis.map(t => t.toFixed(1));
        mpcChart.data.datasets[0].data = xHistory.map(x => x[0]);
        mpcChart.data.datasets[1].data = xHistory.map(x => x[1]);
        mpcChart.data.datasets[2].data = uHistory;
        
        // Add reference line for target position
        const targetLinePlugin = {
          id: 'targetLine',
          afterDraw: (chart) => {
            const yAxis = chart.scales.y;
            const yValue = yAxis.getPixelForValue(targetPos);
            const ctx = chart.ctx;
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(chart.chartArea.left, yValue);
            ctx.lineTo(chart.chartArea.right, yValue);
            ctx.strokeStyle = 'rgba(239, 68, 68, 0.7)'; // red-500
            ctx.lineWidth = 2;
            ctx.setLineDash([6, 6]);
            ctx.stroke();
            ctx.restore();
          }
        };

        // Remove old plugin before adding new one
        mpcChart.options.plugins.targetLine = false; 
        mpcChart.update();
        mpcChart.options.plugins.targetLine = targetLinePlugin;
        mpcChart.update();
    }

    runButton.addEventListener('click', runSimulation);

    // Initial run
    runSimulation();
});
</script>

</body>
</html>